﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;

namespace ExMen
{
    public partial class Form1 : Form
    {

        List<Girl> Girls = new List<Girl>();
        List<Girl> DelGirls = new List<Girl>();

        DateTime actMonth;

        class DayPoint
        {
            public DateTime Dt;
            public Rectangle Pos;
            public int GirlDayInd = -1;

            public DayPoint(DateTime Dt, Rectangle Pos, int GirlDayInd)
            {
                this.Dt = Dt;
                this.Pos = Pos;
                this.GirlDayInd = GirlDayInd;
            }
        }
        List<DayPoint> dp = new List<DayPoint>();

        Font fntNone = new System.Drawing.Font("Arial", 8, FontStyle.Regular);
        Font fntClear = new System.Drawing.Font("Arial", 8, FontStyle.Bold);
        Brush brMaybeClear = new SolidBrush(Color.FromArgb(190, 240, 20));

        public Form1()
        {
            InitializeComponent();
            im.Image = new Bitmap(im.Width, im.Height);
            im1.Image = new Bitmap(im1.Width, im1.Height);

            actMonth = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);

            // временно создаем дувушку
            /*actGirl = new Girl("Тест");
            SaveData();*/

        }

        bool bOk = false;
        public bool ShowDialog(byte[] buf)
        {
            bOk = false;

            LoadBySrsData(buf); // загрузка девочек
            RefillDdlGirl("");  // заполнение списка девушек
            ReDrawCalendar();
            ShowDialog();
            return bOk;
        }

        // заполнение списка девушек
        private void RefillDdlGirl(string sel)
        {
            ddlGirls.Items.Clear();
            int selectInd = -1;
            foreach (Girl grl in Girls)
            {
                ddlGirls.Items.Add(grl.Name);
                if (grl.Name == sel) selectInd = ddlGirls.Items.Count - 1;
            }
            if (selectInd != -1) ddlGirls.SelectedIndex = selectInd;
        }

        const int shX = 10;
        const int shY = 30;
        const int step = 20;
        const int stepMonth = 170;

        List<PossableDayStatus> pDayStats;

        void ReDrawCalendar()
        {
            dp.Clear();
            Graphics gr = Graphics.FromImage(im.Image);
            gr.Clear(Color.White);

            if (actGirl == null)
            {
                gr.Dispose();
                im.Refresh();
                ReDrawLines();
                return;
            }

            DateTime dtMonth = actMonth;

            int len = DateTime.DaysInMonth(dtMonth.AddMonths(-2).Year, dtMonth.AddMonths(-2).Month) +
                 DateTime.DaysInMonth(dtMonth.AddMonths(-1).Year, dtMonth.AddMonths(-1).Month) +
                 DateTime.DaysInMonth(dtMonth.Year, dtMonth.Month) +
                 DateTime.DaysInMonth(dtMonth.AddMonths(1).Year, dtMonth.AddMonths(1).Month) +
                 DateTime.DaysInMonth(dtMonth.AddMonths(2).Year, dtMonth.AddMonths(2).Month);
            pDayStats = GetPossableDayStatuses(dtMonth.AddMonths(-2), len);

            DrawOneMonth(gr, shX, dtMonth.AddMonths(-2));
            DrawOneMonth(gr, shX + stepMonth, dtMonth.AddMonths(-1));
            DrawOneMonth(gr, shX + stepMonth * 2, dtMonth);
            DrawOneMonth(gr, shX + stepMonth * 3, dtMonth.AddMonths(1));
            DrawOneMonth(gr, shX + stepMonth * 4, dtMonth.AddMonths(2));

            gr.Dispose();
            im.Refresh();

            ReDrawLines();
        }

        void ReDrawLines()
        {
            Graphics gr = Graphics.FromImage(im1.Image);
            gr.Clear(Color.White);

            if (actGirl == null)
            {
                gr.Dispose();
                im1.Refresh();
                return;
            }

            DateTime StartDay = actMonth.AddMonths(-2);
            int numDayOfPeriod = GetNumDayOfPeriod(StartDay);

            gr.DrawString("Пн", Font, Brushes.Black, shX, shY - 20);
            gr.DrawString("Вт", Font, Brushes.Black, shX + step, shY - 20);
            gr.DrawString("Ср", Font, Brushes.Black, shX + step * 2, shY - 20);
            gr.DrawString("Чт", Font, Brushes.Black, shX + step * 3, shY - 20);
            gr.DrawString("Пт", Font, Brushes.Black, shX + step * 4, shY - 20);
            gr.DrawString("Сб", fntClear, Brushes.Maroon, shX + step * 5, shY - 20);
            gr.DrawString("Вс", fntClear, Brushes.Maroon, shX + step * 6, shY - 20);
            gr.DrawString("Пн", Font, Brushes.Black, shX + step * 7, shY - 20);
            gr.DrawString("Вт", Font, Brushes.Black, shX + step * 8, shY - 20);
            gr.DrawString("Ср", Font, Brushes.Black, shX + step * 9, shY - 20);
            gr.DrawString("Чт", Font, Brushes.Black, shX + step * 10, shY - 20);
            gr.DrawString("Пт", Font, Brushes.Black, shX + step * 11, shY - 20);
            gr.DrawString("Сб", fntClear, Brushes.Maroon, shX + step * 12, shY - 20);
            gr.DrawString("Вс", fntClear, Brushes.Maroon, shX + step * 13, shY - 20);
            gr.DrawString("Пн", Font, Brushes.Black, shX + step * 14, shY - 20);
            gr.DrawString("Вт", Font, Brushes.Black, shX + step * 15, shY - 20);
            gr.DrawString("Ср", Font, Brushes.Black, shX + step * 16, shY - 20);
            gr.DrawString("Чт", Font, Brushes.Black, shX + step * 17, shY - 20);
            gr.DrawString("Пт", Font, Brushes.Black, shX + step * 18, shY - 20);
            gr.DrawString("Сб", fntClear, Brushes.Maroon, shX + step * 19, shY - 20);
            gr.DrawString("Вс", fntClear, Brushes.Maroon, shX + step * 20, shY - 20);
            gr.DrawString("Пн", Font, Brushes.Black, shX + step * 21, shY - 20);
            gr.DrawString("Вт", Font, Brushes.Black, shX + step * 22, shY - 20);
            gr.DrawString("Ср", Font, Brushes.Black, shX + step * 23, shY - 20);
            gr.DrawString("Чт", Font, Brushes.Black, shX + step * 24, shY - 20);
            gr.DrawString("Пт", Font, Brushes.Black, shX + step * 25, shY - 20);
            gr.DrawString("Сб", fntClear, Brushes.Maroon, shX + step * 26, shY - 20);
            gr.DrawString("Вс", fntClear, Brushes.Maroon, shX + step * 27, shY - 20);

            // вертикальные полосы
            Pen pn = new Pen(Color.LightGray);
            pn.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
            for (int i = 0; i < 28; i++)
                gr.DrawLine(pn, shX + i * step + step, shY - 20, shX + i * step + step, shY + step * 7);
            pn.Dispose();

            int len = DateTime.DaysInMonth(StartDay.Year, StartDay.Month) +
                 DateTime.DaysInMonth(StartDay.AddMonths(1).Year, StartDay.AddMonths(1).Month) +
                 DateTime.DaysInMonth(StartDay.AddMonths(2).Year, StartDay.AddMonths(2).Month) +
                 DateTime.DaysInMonth(StartDay.AddMonths(3).Year, StartDay.AddMonths(3).Month) +
                 DateTime.DaysInMonth(StartDay.AddMonths(4).Year, StartDay.AddMonths(4).Month);

            int y = shY;
            int shText = (step - 15) / 2;
            DateTime firstDayInLine = StartDay;
            DateTime dt = StartDay;
            for (int i = 0; i < len; i++)
            {
                int x = shX + numDayOfPeriod * step;
                dt = StartDay.AddDays(i);
                int GirlDayInd = actGirl.GetDayInd(dt);

                switch (pDayStats[i])
                {
                    case PossableDayStatus.Clear: gr.FillEllipse(Brushes.LightGreen, x + 1, y + 1, step - 3, step - 3); break;
                    case PossableDayStatus.MaybeClear: gr.FillEllipse(brMaybeClear, x + 1, y + 1, step - 3, step - 3); break;
                    case PossableDayStatus.MaybeMen: gr.FillEllipse(Brushes.Orange, x + 1, y + 1, step - 3, step - 3); break;
                    case PossableDayStatus.Men: gr.FillEllipse(Brushes.OrangeRed, x + 1, y + 1, step - 3, step - 3); break;
                }

                Brush br = Brushes.Black;
                if (dt.DayOfWeek == DayOfWeek.Saturday || dt.DayOfWeek == DayOfWeek.Sunday) br = Brushes.Maroon;

                if (GirlDayInd != -1)
                {
                    gr.DrawEllipse(Pens.Black, x + 1, y + 1, step - 3, step - 3);
                    gr.DrawString(dt.ToString("dd"), fntClear, br, x + shText, y + shText);
                }
                else
                {
                    gr.DrawString(dt.ToString("dd"), fntNone, br, x + shText, y + shText);
                }

                // если смена месяца
                if (dt.AddDays(-1).Month != dt.Month && numDayOfPeriod != 0)
                {
                    gr.DrawLine(Pens.Gray, x, y, x, y + step);
                }

                // сегодня
                if (dt == DateTime.Today) gr.DrawRectangle(Pens.Blue, x + 1, y + 1, step - 2, step - 2);

                numDayOfPeriod++;
                if (numDayOfPeriod == 28)
                {
                    gr.DrawString(((firstDayInLine.Month != dt.Month) ? firstDayInLine.ToString("MM.yyyy") + " - " : "") + dt.ToString("MM.yyyy"), fntClear, Brushes.Black, shX + 28 * step + 15, y + shText);
                    firstDayInLine = dt.AddDays(1);
                    numDayOfPeriod = 0;
                    y += step;
                }
            }
            gr.DrawString(((firstDayInLine.Month != dt.Month) ? firstDayInLine.ToString("MM.yyyy") + " - " : "") + dt.ToString("MM.yyyy"), fntClear, Brushes.Black, shX + 28 * step + 15, y + shText);

            gr.Dispose();
            im1.Refresh();
        }

        int GetNumDayOfPeriod(DateTime dt)
        {
            return ((int)(dt - DateTime.MinValue).TotalDays) % 28;
        }

        void DrawOneMonth(Graphics gr, int shX, DateTime dtMonth)
        {
            // шапка
            gr.DrawString(dtMonth.ToString("MM.yyyy"), Font, Brushes.Black, shX + step * 2, shY - 20);

            gr.DrawString("Пн", Font, Brushes.Black, shX, shY);
            gr.DrawString("Вт", Font, Brushes.Black, shX + step, shY);
            gr.DrawString("Ср", Font, Brushes.Black, shX + step * 2, shY);
            gr.DrawString("Чт", Font, Brushes.Black, shX + step * 3, shY);
            gr.DrawString("Пт", Font, Brushes.Black, shX + step * 4, shY);
            gr.DrawString("Сб", Font, Brushes.Black, shX + step * 5, shY);
            gr.DrawString("Вс", Font, Brushes.Black, shX + step * 6, shY);

            int len = DateTime.DaysInMonth(dtMonth.Year, dtMonth.Month);

            int numDayOfWeek = (int)(dtMonth.DayOfWeek) - 1;
            if (numDayOfWeek == -1) numDayOfWeek = 6;

            int shText = (step - 15) / 2;

            int y = shY + step;
            for (int i = 0; i < len; i++)
            {
                int x = shX + numDayOfWeek * step;
                DateTime dt = dtMonth.AddDays(i);
                int GirlDayInd = actGirl.GetDayInd(dt);

                switch (pDayStats[dp.Count])
                {
                    case PossableDayStatus.Clear: gr.FillEllipse(Brushes.LightGreen, x + 1, y + 1, step - 3, step - 3); break;
                    case PossableDayStatus.MaybeClear: gr.FillEllipse(brMaybeClear, x + 1, y + 1, step - 3, step - 3); break;
                    case PossableDayStatus.MaybeMen: gr.FillEllipse(Brushes.Orange, x + 1, y + 1, step - 3, step - 3); break;
                    case PossableDayStatus.Men: gr.FillEllipse(Brushes.OrangeRed, x + 1, y + 1, step - 3, step - 3); break;
                }

                if (GirlDayInd != -1)
                {
                    gr.DrawEllipse(Pens.Black, x + 1, y + 1, step - 3, step - 3);
                    gr.DrawString((i + 1).ToString("00"), fntClear, Brushes.Black, x + shText, y + shText);

                }
                else
                {
                    gr.DrawString((i + 1).ToString("00"), fntNone, Brushes.Black, x + shText, y + shText);
                }

                // сегодня
                if (dt == DateTime.Today) gr.DrawRectangle(Pens.Blue, x + 1, y + 1, step - 2, step - 2);

                dp.Add(new DayPoint(dt, new Rectangle(x, y, step, step), GirlDayInd));

                numDayOfWeek++;
                if (numDayOfWeek == 7)
                {
                    numDayOfWeek = 0;
                    y += step;
                }
            }
        }

        List<PossableDayStatus> GetPossableDayStatuses(DateTime StartDay, int len)
        {
            List<PossableDayStatus> ret = new List<PossableDayStatus>();
            // заполняем возможные чистые дни
            for (int i = 0; i < len; i++)
            {
                DateTime dt = StartDay.AddDays(i);
                ret.Add(GetPossableDayStat(dt));
            }

            // заполняем возможные вероятно-чистые дни
            int lastClearInd = -1;
            for (int i = 0; i < len; i++)
            {
                /*DateTime dt = StartDay.AddDays(i);
                if (dt > new DateTime(2013, 12, 1))
                {
                    i = i;
                }*/

                // если между чистыми днями меньше трех дней, они вероятно-чистые
                if (ret[i] == PossableDayStatus.Clear)
                {
                    if (lastClearInd != -1 && (i - lastClearInd) > 1)
                    {
                        if ((i - lastClearInd) < 4)
                        {
                            for (int j = (lastClearInd + 1); j < i; j++)
                                ret[j] = PossableDayStatus.MaybeClear;
                        }
                    }
                    lastClearInd = i;
                }
            }

            // заполняем наиболее вероятные дни менструации
            // (дни, отстоящие максимально далеко)

            return ret;
        }

        // проверка на возможность
        PossableDayStatus GetPossableDayStat(DateTime dt)
        {
            DataDay findDay = null;

            // находим, есть ли совпадение кратное 28
            int DayDistance = 0; // разрыв между датами (в днях)
            for (int i = 0; i < actGirl.dl.Count; i++)
            {
                // экстерполирование вперед
                if (((int)(actGirl.dl[i].Dt.Date - dt.Date).TotalDays) % 28 == 0 && actGirl.dl[i].Dt.Date <= dt.Date)
                {
                    findDay = actGirl.dl[i];
                    DayDistance = Math.Abs((int)(actGirl.dl[i].Dt.Date - dt.Date).TotalDays);
                }
            }

            // если совпадение есть, возвращаем
            if (findDay != null)
            {
                GetExterStat(findDay, DayDistance);
            }
            // если совпадений вперед не найдено, экстерполируем назад
            else
            {
                for (int i = 0; i < actGirl.dl.Count; i++)
                {
                    // экстерполирование назад
                    if (((int)(actGirl.dl[i].Dt.Date - dt.Date).TotalDays) % 28 == 0 && actGirl.dl[i].Dt.Date > dt.Date)
                    {
                        findDay = actGirl.dl[i];
                        DayDistance = Math.Abs((int)(actGirl.dl[i].Dt.Date - dt.Date).TotalDays);
                        break;
                    }
                }

            }

            return GetExterStat(findDay, DayDistance);
        }

        PossableDayStatus GetExterStat(DataDay findDay, int DayDistance)
        {
            if (findDay == null) return PossableDayStatus.None;
            if (findDay.Stat == DayStatus.MaybeClear) return PossableDayStatus.MaybeClear;
            if (findDay.Stat == DayStatus.MaybeMen) return PossableDayStatus.MaybeMen;
            if (findDay.Stat == DayStatus.Men)
            {
                // если больше 3 периодов
                if (DayDistance > 84) return PossableDayStatus.MaybeMen;
                else return PossableDayStatus.Men;
            }
            if (findDay.Stat == DayStatus.Clear)
            {
                // если больше 3 периодов
                if (DayDistance > 84) return PossableDayStatus.MaybeClear;
                else return PossableDayStatus.Clear;
            }
            return PossableDayStatus.None;
        }

        enum PossableDayStatus { None, Clear, MaybeClear, MaybeMen, Men }

        enum DayStatus { Clear, MaybeClear, MaybeMen, Men }

        class DataDay
        {
            public DateTime Dt;
            public DayStatus Stat;
            public DataDay(DateTime Dt, DayStatus Stat) { this.Dt = Dt; this.Stat = Stat; }
        }

        class Girl : IComparable<Girl>
        {
            public string Name;
            public List<DataDay> dl = new List<DataDay>();

            public Girl(string Name) { this.Name = Name; }

            public Girl(BinaryReader br) { LoadData(br); }

            public int GetDayInd(DateTime dt)
            {
                for (int i = 0; i < dl.Count; i++) if (dl[i].Dt.Date == dt.Date) return i;
                return -1;
            }

            void LoadData(BinaryReader br)
            {
                br.ReadInt32(); // задел на версионность

                Name = br.ReadString(); // имя

                int kolVo = br.ReadInt32();
                for (int i = 0; i < kolVo; i++)
                {
                    dl.Add(new DataDay(new DateTime(br.ReadInt64()), (DayStatus)br.ReadByte()));
                }
            }

            public void Save(BinaryWriter bw)
            {
                bw.Write(0); // задел на версионность

                bw.Write(Name); // имя

                bw.Write(dl.Count);
                for (int i = 0; i < dl.Count; i++)
                {
                    bw.Write(dl[i].Dt.Ticks);
                    bw.Write((byte)dl[i].Stat);
                }
            }

            public int CompareTo(Girl other)
            {
                return Name.Trim().ToUpper().CompareTo(other.Name.Trim().ToUpper());
            }
        }

        Girl actGirl = null;

        #region *** Save/Load *******************************************************

        void LoadBySrsData(byte[] buf)
        {
            Girls.Clear();
            DelGirls.Clear();

            using (MemoryStream ms = new MemoryStream(buf))
            {
                using (BinaryReader br = new BinaryReader(ms, Encoding.UTF8))
                {
                    br.ReadInt32(); // задел на версионность

                    // девушки
                    int kolvo = br.ReadInt32();
                    for (int i = 0; i < kolvo; i++) Girls.Add(new Girl(br));

                    // удаленые девушки
                    kolvo = br.ReadInt32();
                    for (int i = 0; i < kolvo; i++) DelGirls.Add(new Girl(br));
                }
            }
        }

        public byte[] GetSrsData()
        {
            using (MemoryStream ms = new MemoryStream())
            {
                using (BinaryWriter bw = new BinaryWriter(ms, Encoding.UTF8))
                {
                    bw.Write(0); // задел на версионность

                    // девушки
                    bw.Write(Girls.Count);
                    for (int i = 0; i < Girls.Count; i++) Girls[i].Save(bw);

                    // удаленые девушки
                    bw.Write(DelGirls.Count);
                    for (int i = 0; i < DelGirls.Count; i++) DelGirls[i].Save(bw);

                    bw.Flush();
                    return ms.ToArray();
                }
            }
        }

        #endregion *** Save/Load *******************************************************

        private void im_MouseMove(object sender, MouseEventArgs e)
        {
            // определение даты
            /*DayPoint day = null;
            for (int i = 0; i < dp.Count; i++)
            {
                if (e.X >= dp[i].Pos.X && e.Y >= dp[i].Pos.Y && e.X < dp[i].Pos.Right && e.Y < dp[i].Pos.Bottom) { day = dp[i]; break; }
            }

            if (day == null) return;*/
        }

        private void im_MouseClick(object sender, MouseEventArgs e)
        {
            // определение даты
            DayPoint day = null;
            for (int i = 0; i < dp.Count; i++)
            {
                if (e.X >= dp[i].Pos.X && e.Y >= dp[i].Pos.Y && e.X < dp[i].Pos.Right && e.Y < dp[i].Pos.Bottom) { day = dp[i]; break; }
            }

            if (day == null) return;

            if (btnErase.Checked)
            {
                if (day.GirlDayInd != -1) actGirl.dl.RemoveAt(day.GirlDayInd);
            }
            else
            {
                DayStatus addDayStat = DayStatus.Clear;
                if (btnAddMaybeClear.Checked) addDayStat = DayStatus.MaybeClear;
                if (btnAddMaybeMen.Checked) addDayStat = DayStatus.MaybeMen;
                if (btnAddMen.Checked) addDayStat = DayStatus.Men;

                if (day.GirlDayInd == -1)
                {
                    actGirl.dl.Add(new DataDay(day.Dt, addDayStat));
                }
                else
                {
                    if (actGirl.dl[day.GirlDayInd].Stat == addDayStat) actGirl.dl.RemoveAt(day.GirlDayInd);
                    else actGirl.dl[day.GirlDayInd].Stat = addDayStat;
                }
            }

            //SaveGirl(actGirl);
            ReDrawCalendar();
        }

        private void ddlGirls_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (ddlGirls.SelectedIndex == -1) actGirl = null;
            else actGirl = Girls[ddlGirls.SelectedIndex];
            ReDrawCalendar();
        }

        private void btnMonthBack_Click(object sender, EventArgs e)
        {
            actMonth = actMonth.AddMonths(-1);
            ReDrawCalendar();
        }

        private void btnMonthFor_Click(object sender, EventArgs e)
        {
            actMonth = actMonth.AddMonths(1);
            ReDrawCalendar();
        }

        private void btnAdd_Click(object sender, EventArgs e)
        {
            btnAddClear.Checked = sender == btnAddClear;
            btnAddMaybeClear.Checked = sender == btnAddMaybeClear;
            btnAddMaybeMen.Checked = sender == btnAddMaybeMen;
            btnAddMen.Checked = sender == btnAddMen;
            btnErase.Checked = sender == btnErase;
        }

        private void btnAddGirl_Click(object sender, EventArgs e)
        {
            ESpace.Comm.EditValueShowForm("Добавление девушки", "", new ESpace.ConfirmClickDelegate(AddGirl_Confirm));
        }

        // подтверждение создания новой девушки
        bool AddGirl_Confirm(string val)
        {
            string nm = val.Trim();

            // проверка на недопустимые символы
            if (nm == "")
            {
                MessageBox.Show("Имя не может быть пустым");
                return false;
            }

            // проверка на дубликат
            if (GirlExist(nm, Girls))
            {
                MessageBox.Show("Такое имя уже существует");
                return false;
            }

            Girls.Add(new Girl(nm));
            Girls.Sort();
            RefillDdlGirl(nm);

            return true;
        }

        private void btnDelGirl_Click(object sender, EventArgs e)
        {
            if (ddlGirls.SelectedIndex == -1) return;

            Girl grlForGel = Girls[ddlGirls.SelectedIndex];

            if (MessageBox.Show("Действительно удалить девушку '" + grlForGel.Name + "'?", "Удаление девушки", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == System.Windows.Forms.DialogResult.No) return;

            string srsName = grlForGel.Name;
            string delName = srsName;
            int delInd = 0;
            while (GirlExist(delName, DelGirls))
            {
                delInd++;
                delName = srsName + "[" + delInd + "]";
            }

            Girls.Remove(grlForGel);
            grlForGel.Name = delName;
            DelGirls.Add(grlForGel);

            actGirl = null;
            RefillDdlGirl("");
            ReDrawCalendar();
        }

        bool GirlExist(string FindName, List<Girl> GirlList)
        {
            foreach (Girl grl in GirlList) if (grl.Name.Trim().ToUpper() == FindName.Trim().ToUpper()) return true;
            return false;
        }

        private void btnOK_Click(object sender, EventArgs e)
        {
            bOk = true;
            Close();
        }

        private void btnCancel_Click(object sender, EventArgs e)
        {
            Close();
        }

    }
}
